package pt.isel.pdm.ouryamba;

import java.util.Date;
import java.util.LinkedList;
import java.util.List;
import java.util.concurrent.atomic.AtomicReference;
import winterwell.jtwitter.Twitter;
import winterwell.jtwitter.Twitter.Status;
import winterwell.jtwitter.Twitter.User;
import android.support.v4.app.NotificationCompat;
import android.app.Application;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.ContentValues;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.Cursor;
import android.net.ConnectivityManager;
import android.net.Uri;
import android.preference.PreferenceManager;
import android.util.Log;
import android.widget.Toast;

public class General extends Application
{
	
	public SharedPreferences prefs;
	private Integer maxChars;
	private Integer maxMsgToShow;
	private Integer maxMsgStored;
	private Boolean automaticUpdateEnable;
	private Integer automaticUpdateInterval;
	private String username;
	private Long statusSelectedId;
	private boolean servicesStarted;
	private Twitter twitter;
	public static final String TAG = "PDM";
	public static final Long DEFAULT_STATUS_SELECTED_ID = Long.valueOf(-1);
	public static final Integer MAXIMUM_STATUS_LENGTH = 140;
	public static final Integer DEFAULT_MSG_TO_SHOW_NUMBER = 20;
	public static final Integer DEFAULT_MSG_TO_STORE_NUMBER = 20;
	public static final Integer DEFAULT_AUTOMATIC_UPDATE_INTERVAL = 10000;
	public static final String STATUS_LENGTH_PREF_KEY = "lengthPref";
	public static final String USER_PREF_KEY = "userPref";
	public static final String PASSWORD_PREF_KEY = "passPref";
	public static final String URL_PREF_KEY = "urlPref";
	public static final String MSG_NUMBER_TO_SHOW_PREF_KEY = "msgNumberPref";
	public static final String MSG_NUMBER_TO_STORE_PREF_KEY = "numberOfMessages2StorePref";
	public static final String AUTOMATIC_UPDATE_PREF_KEY = "automaticUpdatePref";
	public static final String AUTOMATIC_UPDATE_PERIOD_PREF_KEY = "automaticUpdatePeriodPref";
	public static final String EMPTY_STRING = "";
	public static final String SPACE_STRING = " ";
	public AtomicReference<TimelineActivity> timelineActivity;
	public final Object waiterForNetworkLock = new Object();
	public final Waiter waiter = new Waiter();
	private final Object timelineNotifLock = new Object();
	private boolean timelineSeen = false;
	private long timelineSeenId = 0;
	private NotificationManager notificationManager;
	private static final int NEW_STATUS_NOTIFICATION = 0, STATUS_ERROR_NOTIFICATION = 1;
	private static final String NOTIFICATION_TITLE = "OurYamba";
	
	
//Own methods

	/**
	 * Fetch the updated value of {@code maxChars} and return it.</br>
	 * 
	 * If the value {@code maxChars} is null (hasn't being initialized or the value in preferences
	 * was changed) fetches the most updated value from the preferences and return it.
	 * Otherwise (the value is initialized and the preferences didn't changed) simply return the
	 * value.
	 * 
	 * @return The updated value of {@code maxChars}.
	 * */
	public synchronized int getMaxChars()
	{
		if(maxChars == null)
		{
			String value = prefs.getString(STATUS_LENGTH_PREF_KEY, MAXIMUM_STATUS_LENGTH.toString());
			if(value.isEmpty())
			{
				maxChars = MAXIMUM_STATUS_LENGTH;
			}
			else
			{
				maxChars = Integer.valueOf(value);
			}
		}
		return maxChars;
	}
	
	/**
	 * Reset the value of {@code maxChars}.</br>
	 * 
	 * One case of its use is when the value in preferences was changed. It set the reference to
	 * null indicating it have to be fetched from the preferences the next time it is needed.
	 * */
	public synchronized void resetMaxChars()
	{
		maxChars = null;
	}
	
	/**
	 * Fetch the updated value of {@code maxMsg} and return it.</br>
	 * 
	 * If the value {@code maxMsg} is null (hasn't being initialized or the value in preferences
	 * was changed) fetches the most updated value from the preferences and return it.
	 * Otherwise (the value is initialized and the preferences didn't changed) simply return the
	 * value.
	 * 
	 * @return The updated value of {@code maxMsg}.
	 * */
	public synchronized int getMaxMsgToShow()
	{
		if(maxMsgToShow == null)
		{
			String value = prefs.getString(MSG_NUMBER_TO_SHOW_PREF_KEY, DEFAULT_MSG_TO_SHOW_NUMBER.toString());
			if(value.isEmpty())
			{
				maxMsgToShow = DEFAULT_MSG_TO_SHOW_NUMBER;
			}
			else
			{
				maxMsgToShow = Integer.valueOf(value);
			}
		}
		return maxMsgToShow;
	}
	
	/**
	 * Reset the value of {@code maxMsg}.</br>
	 * 
	 * One case of its use is when the value in preferences was changed. It set the reference to
	 * null indicating it have to be fetched from the preferences the next time it is needed.
	 * */
	public synchronized void resetMaxMsgToShow()
	{
		maxMsgToShow = null;
	}
	
	/**
	 * Fetch the updated value of {@code maxMsgStored} and return it.</br>
	 * 
	 * If the value {@code maxMsgStored} is null (hasn't being initialized or the value in preferences
	 * was changed) fetches the most updated value from the preferences and return it.
	 * Otherwise (the value is initialized and the preferences didn't changed) simply return the
	 * value.
	 * 
	 * @return The updated value of {@code maxMsgStored}.
	 * */
	public synchronized int getMaxMsgStored()
	{
		if(maxMsgStored == null)
		{
			String value = prefs.getString(MSG_NUMBER_TO_STORE_PREF_KEY, DEFAULT_MSG_TO_SHOW_NUMBER.toString());
			if(value.isEmpty())
			{
				maxMsgStored = DEFAULT_MSG_TO_STORE_NUMBER;
			}
			else
			{
				maxMsgStored = Integer.valueOf(value);
			}
		}
		return maxMsgStored;
	}
	
	/**
	 * Reset the value of {@code maxMsgStored}.</br>
	 * 
	 * One case of its use is when the value in preferences was changed. It set the reference to
	 * null indicating it have to be fetched from the preferences the next time it is needed.
	 * */
	public synchronized void resetMaxMsgStored()
	{
		maxMsgStored = null;
	}
	
	/**
	 * Fetch the boolean value indicating whether the automatic updates are enable or not.</br>
	 * 
	 * If the value {@code automaticUpdateEnable} is null, then a new value is fetched from the
	 * preferences and stored. Otherwise is returned the actual value.
	 * Always retrieves the most updated value from preferences.
	 * */
	public synchronized boolean getAutomaticUpdate()
	{
		if(automaticUpdateEnable == null)
		{
			automaticUpdateEnable = prefs.getBoolean(AUTOMATIC_UPDATE_PREF_KEY, false);
		}
		return automaticUpdateEnable;
	}
	
	/**
	 * Reset the value of {@code automaticUpdateEnable} and {@code automaticUpdateInterval} variables.</br>
	 * 
	 * This flags that next time the variable {@code automaticUpdateEnable} is accessed, needs to
	 * be fetched from the preferences.
	 * Also reset the automatic updates interval because it won't be used.
	 * 
	 * @see #resetAutomaticUpdateInterval()
	 * */
	public synchronized void resetAutomaticUpdate()
	{
		automaticUpdateEnable = null;
		resetAutomaticUpdateInterval();
	}
	
	/**
	 * Fetch the value of {@code automaticUpdateInterval} and return it.</br>
	 * 
	 * If the value {@code automaticUpdateInterval} is zero (hasn't being initialized or the 
	 * value in preferences was changed) fetches the most updated value from the preferences and
	 * return it. Otherwise (the value is initialized and the preferences didn't changed) simply
	 * return the value.
	 * */
	public synchronized Integer getAutomaticUpdateInterval()
	{
		if(automaticUpdateInterval == null)
		{
			String aux = prefs.getString(AUTOMATIC_UPDATE_PERIOD_PREF_KEY, DEFAULT_AUTOMATIC_UPDATE_INTERVAL.toString());
			if(aux.isEmpty())
			{
				automaticUpdateInterval = DEFAULT_AUTOMATIC_UPDATE_INTERVAL;
			}
			else
			{
				automaticUpdateInterval = Integer.valueOf(aux);
			}
		}
		if(automaticUpdateInterval == 0)
		{
			automaticUpdateInterval = DEFAULT_AUTOMATIC_UPDATE_INTERVAL;
		}
		return automaticUpdateInterval;
	}
	
	/**
	 * Reset the value of {@code automaticUpdateInterval} variable.</br>
	 * 
	 * One case of its use is when the value in preferences was changed. It set the value to
	 * zero indicating it have to be fetched from the preferences the next time it is needed.
	 * */
	public synchronized void resetAutomaticUpdateInterval()
	{
		automaticUpdateInterval = null;
	}
	
	/**
	 * Fetch the value of {@code username} and return it.</br>
	 * 
	 * If the value {@code username} is null (hasn't being initialized or the 
	 * value in preferences was changed) fetches the most updated value from the preferences and
	 * return it. Otherwise (the value is initialized and the preferences didn't changed) simply
	 * return the value.
	 * */
	public synchronized String getUsername()
	{
		if(username == null)
		{
			username = prefs.getString(USER_PREF_KEY, EMPTY_STRING);
		}
		return username;
	}
	
	/**
	 * Reset the value of {@code username} variable.</br>
	 * 
	 * One case of its use is when the value in preferences was changed. It set the value to
	 * zero indicating it have to be fetched from the preferences the next time it is needed.
	 * Also resets the {@code twitter} variable because now it have new configurations.
	 * */
	public synchronized void resetUsername()
	{
		username = null;
		resetTwitter();
	}
	
	/**
	 * Initialize and return the {@code twitter} object.</br>
	 * 
	 * If the {@code twitter} hasn't being initiated yet or if the preferences were changed,
	 * the object is initialized, otherwise simply return the existing reference.
	 * This behavior is justified with the change of login preferences (user, password
	 * and URL) that can happen any time during the application lifetime and those
	 * changes can invalidate the {@code twitter} connection. This way, every time a component
	 * needs the {@code twitter} object, this method is called and the preferences changes
	 * are reflected here.</br>
	 * 
	 * Also sets the maximum number of status to be fetched in one request, according to the value
	 * number of messages to store, from the preferences.
	 * 
	 * @see #getMaxMsgStored()
	 * @return The {@code twitter} reference already initiated with the latest preferences.
	 * */
	public synchronized Twitter getTwitter()
	{
		if(twitter == null)
		{
			twitter = new Twitter(prefs.getString(USER_PREF_KEY, EMPTY_STRING), prefs.getString(PASSWORD_PREF_KEY, EMPTY_STRING));
			twitter.setAPIRootUrl(prefs.getString(URL_PREF_KEY, EMPTY_STRING));
			twitter.setMaxResults(getMaxMsgStored());
		}
		return twitter;
	}
	
	/**
	 * Reset the value of {@code twitter}.</br>
	 * 
	 * One case of its use is when the values in preferences were changed. It set the reference to
	 * null indicating that the object must be created again but with new values fetched from the 
	 * preferences, the next time the reference is needed.
	 * */
	public synchronized void resetTwitter()
	{
		twitter = null;
	}
		
	/**
	 * Fetch the selected item from the ListView.</br>
	 * 
	 * If there is none value, it is returned a default value known to everyone.
	 * 
	 * @return The ID of the status select from the ListView (TimelineActivity).
	 * */
	public synchronized Long getStatusSelectedId()
	{
		if(statusSelectedId == null)
		{
			statusSelectedId = DEFAULT_STATUS_SELECTED_ID;
		}
		return statusSelectedId;
	}
	
	/**
	 * Set the new value for the item select from the ListView (TimelineActivity).</br>
	 * */
	public synchronized void setStatusSelectedId(Long value)
	{
		statusSelectedId = value;
	}

	/**
	 * Broadcast the intent to send an email.</br>
	 * 
	 * It is create a chooser panel so the user can choose from the mail applications that he have which one he prefer to use.
	 * 
	 * @param recipient Is the destination email.
	 * @param subject Is the subject of the email.
	 * @param message Is the content message of the email.
	 * */
	public void sendEmail(String recipient, String subject, String message) 
	{
	    try 
	    {
	        Intent emailIntent = new Intent(android.content.Intent.ACTION_SEND);
	        emailIntent.setType("plain/text");
	        if (recipient != null)
	        {
	        	emailIntent.putExtra(android.content.Intent.EXTRA_EMAIL, new String[]{recipient});
	        }
	        if (subject != null)
	        {
	        	emailIntent.putExtra(android.content.Intent.EXTRA_SUBJECT, subject);
	        }	        	
	        if (message != null)
	        {
	        	emailIntent.putExtra(android.content.Intent.EXTRA_TEXT, message);
	        }
	        startActivity(Intent.createChooser(emailIntent, getString(R.string.sendEmailBox)).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK));
	    }
	    catch (Exception e)
	    {
	    	log("General.sendEmail(): Exception thrown.");
	    	logException(e);
	    	showToast(R.string.shareError);
	    }
	}
	
	/**
	 * Start the StatusUploadService if it hasn't being started yet.
	 * */
	public void startServices()
	{
		if(servicesStarted)
		{
			return;
		}
		Intent intent = new Intent(getApplicationContext(), StatusUploadService.class);
		startService(intent);
	}

	/**
	 * Verify if the user name, password and API URL preferences are filled.</br>
	 * Checks if the values are empty or null.
	 * 
	 * @return True if none of the values are empty or null. Otherwise return false.
	 * */
	public boolean assertPreferences()
	{
		String user = prefs.getString(USER_PREF_KEY, null); 
		String password = prefs.getString(PASSWORD_PREF_KEY, null); 
		String url = prefs.getString(URL_PREF_KEY, null);
		if( user == null || user.isEmpty() ||
			password == null || password.isEmpty() ||
			url == null || url.isEmpty())
		{
			return false;
		}
		return true;
	}
	
	/**
	 * Displays a Toast element on the screen.
	 * 
	 * @param messageID Is the id of the message that will be fetch from resources and displayed.
	 * */
	public void showToast(int messageID)
	{
		Toast.makeText(getApplicationContext(), messageID, Toast.LENGTH_LONG).show();
	}
	
	/**
	 * Logs one message in the application log with the specific tag.
	 * 
	 * @param message Is the message to log.
	 * */
	public void log(String message)
	{
		if(message != null)
		{
			Log.v(TAG, message);
		}
	}
	
	/**
	 * Logs an exception in the application log with the specific tag.
	 * 
	 * Log the exception message (if exists) and the stack trace (if exists).
	 * 
	 * @param exception Is the exception to log.
	 * @see #log(String)
	 * @see #log(StackTraceElement[])
	 * */
	public void logException(Exception exception)
	{
		if(exception.getMessage() != null &&  !exception.getMessage().isEmpty())
		{
			log(exception.getMessage());
		}
		if(exception.getStackTrace() != null)
		{
			if(exception.getStackTrace() != null)
			{
				for(StackTraceElement elem : exception.getStackTrace())
				{
					log(elem.toString());
				}
			}
		}
	}
	
	/**
	 * Verify if the device has connectivity with the network.
	 * 
	 * @return True if the device has connectivity with the network or false otherwise.
	 * */
	public boolean hasNetworkConnection()
	{
		try 
		{
			ConnectivityManager cm = (ConnectivityManager) getApplicationContext().getSystemService(CONNECTIVITY_SERVICE);
			return cm != null && cm.getActiveNetworkInfo() != null && cm.getActiveNetworkInfo().isConnected();
		} 
		catch (Exception e) 
		{
			log("General.hasNetworkConnection(): Exception thrown.");
			logException(e);
		}
		return false;
	}  
	
	/**
	 * Fetch the status with the highest ID from the database.
	 * 
	 * @return The status with higher ID from the database.
	 * */
	@SuppressWarnings("deprecation")
	public Status dbSelectHighestID()
	{
		String selection = DbStatusContract.COMMITTED + " = ?";
		String[] argSel = new String[]{"1"};
		String orderBy = DbStatusContract.ID + " DESC LIMIT " + getMaxMsgToShow();
		Cursor cursor = null;
		try
		{
			cursor = getContentResolver().query(DbStatusContract.CONTENT_URI, null, selection, argSel, orderBy);
			if(cursor.moveToFirst())
			{
				String message = cursor.getString(DbStatusContract.MESSAGE_POS);
				Long id = cursor.getLong(DbStatusContract.ID_POS);
				String username = cursor.getString(DbStatusContract.USER_NAME_POS);
				Status status = new Status(new User(username), message, id);
				status.createdAt = new Date(cursor.getString(DbStatusContract.CREATED_AT_POS));
				cursor.close();
				cursor = null;
				return status;
			}
			return null;
		}
		catch (Exception e)
		{
			log("Genereal.dbSelectHighestID(): Exception thrown.");
			logException(e);
			return null;
		}
		finally
		{
			if(cursor != null)
			{
				cursor.close();
				cursor = null;
			}
		}
	}
	
	/**
	 * Fetch the status that match the ID parameter.
	 * 
	 * @param The ID of the status to fetch.
	 * @return The status that match the ID parameter. Can be null if the status don't exist.
	 * */
	@SuppressWarnings("deprecation")
	public Status dbSelectByID(long id)
	{
		Cursor cursor = null;
		try
		{
			Uri uri = Uri.withAppendedPath(DbStatusContract.CONTENT_URI, String.valueOf(id));
			cursor = getContentResolver().query(uri, null, null, null, null);
			if(cursor.moveToFirst())
			{
				String message = cursor.getString(DbStatusContract.MESSAGE_POS);
				String username = cursor.getString(DbStatusContract.USER_NAME_POS);
				Status status = new Status(new User(username), message, id);
				status.createdAt = new Date(cursor.getString(DbStatusContract.CREATED_AT_POS));
				cursor.close();
				cursor = null;
				return status;
			}
			return null;
		}
		catch (Exception e)
		{
			log("Genereal.dbSelectByID(): Exception thrown.");
			logException(e);
			return null;
		}
		finally
		{
			if(cursor != null)
			{
				cursor.close();
				cursor = null;
			}
		}
	}
	
	/**
	 * Fetch the collection of status that were uploaded or not, according to the parameter. 
	 * 
	 * @param committed Indicate which type of status should be fetched: the ones that were not sent to the server
	 * 		yet, or the ones already received from the server.
	 * @return The collection of status that match the parameter.
	 * */
	@SuppressWarnings("deprecation")
	public List<Status> dbSelectByCommittedValue(boolean committed)
	{
		String[] selArgs = new String[1];
		if(committed)
		{
			selArgs[0] = "1";
		}
		else
		{
			selArgs[0] = "0";
		}
		String selection = DbStatusContract.COMMITTED + " = ?";
		String orderBy = DbStatusContract.ID + " DESC LIMIT " + getMaxMsgToShow();
		Cursor cursor = null;
		List<Status> list = new LinkedList<Status>();
		try
		{
			cursor = getContentResolver().query(DbStatusContract.CONTENT_URI, null, selection, selArgs, orderBy);
			while(cursor.moveToNext())
			{
				String message = cursor.getString(DbStatusContract.MESSAGE_POS);
				long id = cursor.getLong(DbStatusContract.ID_POS);
				String username = cursor.getString(DbStatusContract.USER_NAME_POS);
				Status status = new Status(new User(username), message, id);
				status.createdAt = new Date(cursor.getString(DbStatusContract.CREATED_AT_POS));
				list.add(status);
			}
			return list;
		}
		catch (Exception e)
		{
			log("Genereal.dbSelectByCommittedValue(): Exception thrown.");
			logException(e);
			list.clear();
			return list;
		}
		finally
		{
			if(cursor != null)
			{
				cursor.close();
				cursor = null;
			}
		}
	}
	
	/**
	 * Insert a new status on the database.
	 * 
	 * @param id Is the ID of the new status.
	 * @param createdAt Is the create date of the new status.
	 * @param message Is the message of the new status.
	 * @param username Is the username of the author of the new status.
	 * @param committed Indicates whether the status is from the server or from the StatusActivity.
	 * */
	public void dbInsert(long id, String createdAt, String message, String username, boolean commited)
	{
		ContentValues cv = new ContentValues();
		cv.put(DbStatusContract.ID, id);
		cv.put(DbStatusContract.CREATED_AT, createdAt.toString());
		cv.put(DbStatusContract.MESSAGE, message);
		cv.put(DbStatusContract.USER_NAME, username);
		cv.put(DbStatusContract.COMMITTED, commited);
    	try
		{
    		getContentResolver().insert(DbStatusContract.CONTENT_URI, cv);
		}
		catch (Exception e)
		{
			log("General.dbInsert(): Exception thrown.");
			logException(e);
		}
	}
	
	/**
	 * Insert a collection of statuses into database.
	 * 
	 * @param list Is the list of new statuses to insert.
	 * @param committed Indicate which "category" the statuses belong: from the server or from the StatusActivity.
	 * */
	public void dbInsertMany(List<Status> list, boolean commited)
	{
		ContentValues cv = new ContentValues();
    	try
		{
    		int count = 0;
    		for(Twitter.Status status : list)
			{
				cv.put(DbStatusContract.ID, status.id);
				cv.put(DbStatusContract.CREATED_AT, status.createdAt.toString());
				cv.put(DbStatusContract.MESSAGE, status.text);
				cv.put(DbStatusContract.USER_NAME, status.getUser().getName());
				cv.put(DbStatusContract.COMMITTED, commited);
				getContentResolver().insert(DbStatusContract.CONTENT_URI, cv);
				cv.clear();
				if(++count == getMaxMsgStored())
				{
					break;
				}
			}
    		log("General.dbInsertMany(): " + count + " status written into database.");
		}
		catch (Exception e)
		{
			log("General.dbInsertMany(): Exception thrown.");
			logException(e);
		}
	}
	
	/**
	 * Delete a single status from the database.
	 * 
	 * @param status Contains the information about the status we want to delete.
	 * @param committed Indicates whether the status is from the server or from the StatusActivity.
	 * */
	public void dbDelete(Status status, boolean committed)
	{
		int rows;
		String[] selArgs = new String[4];
		String where = DbStatusContract.ID + " = ? AND "
				+ DbStatusContract.CREATED_AT + " = ? AND "
				+ DbStatusContract.MESSAGE + " = ? AND "
				+ DbStatusContract.COMMITTED + " = ? ";
		try
		{
			selArgs[DbStatusContract.ID_POS] = String.valueOf(status.id);
			selArgs[DbStatusContract.CREATED_AT_POS] = status.createdAt.toString();
			selArgs[DbStatusContract.MESSAGE_POS] = status.text;
			if(committed)
			{
				selArgs[DbStatusContract.COMMITTED_POS - 1] = "1";				
			}
			else
			{
				selArgs[DbStatusContract.COMMITTED_POS - 1] = "0";
			}
			rows = getContentResolver().delete(DbStatusContract.CONTENT_URI, where, selArgs);
			if(rows == 0)
			{
				log("General.dbDelete(): The status was not deleted from the database.");
			}
		}
		catch (Exception e)
		{
			log("General.dbDelete(): Exception thrown.");
			logException(e);
		}
	}
	
	/**
	 * Delete a collection of statuses that match the parameter.
	 * 
	 * @param committed Indicates whether the statuses are from the server or from StatusActivity.
	 * */
	public void dbDeleteByCommitedValue(boolean committed)
	{
		int rows;
		String[] selArgs = new String[1];
		String where = DbStatusContract.COMMITTED + " = ? ";
		try
		{
			if(committed)
			{
				selArgs[0] = "1";				
			}
			else
			{
				selArgs[0] = "0";
			}
			rows = getContentResolver().delete(DbStatusContract.CONTENT_URI, where, selArgs);
			if(rows == 0)
			{
				log("General.dbDelete(): The status was not deleted from the database.");
			}
		}
		catch (Exception e)
		{
			log("General.dbDeleteByCommitedValue(): Exception thrown.");
			logException(e);
		}
	}
	
	/**
	 * Convert a list of Status objects into a list of Post objects.
	 * 
	 * @return A list of Post objects.
	 * */
	public List<Post> convertToPostList(List<Status> list) 
	{
		List<Post> _list = new LinkedList<Post>();
		for(Status status : list)
		{
			_list.add(new Post(status));
		}
		return _list;
	}

	/**
	 * Signalizes that the TimelineActivity was already seen in the past.</br>
	 * 
	 * Cancels any notification about new statuses that is being shown.
	 * 
	 * @param lastId Is the last highest ID of a status that was seen. 
	 * */
	public void timelineSeen(long lastId)
	{
		synchronized (timelineNotifLock)
		{
			timelineSeenId = lastId;
			timelineSeen = true;
			getNotificationManager().cancel(NEW_STATUS_NOTIFICATION);
		}
	}
	
	/**
	 * Is responsible to show the notifications of new statuses if the TimelineActivity was seen in the past
	 * and if it is not being seen now.
	 * 
	 * @param lastId Is the highest ID of the status from the updated list of status received from the server.
	 * 
	 * @see #showNotification(int, int)
	 * */
	public void timelineUpdated(long lastId)
	{
		if(timelineActivity.get() != null && timelineActivity.get().hasWindowFocus())
		{
			return;
		}
		synchronized(timelineNotifLock)
		{
			if(!timelineSeen)
			{
				return;
			}
			if(timelineSeenId != lastId)
			{
				timelineSeen = false;
				showNotification(R.string.notifTimelineUpdated, NEW_STATUS_NOTIFICATION);
			}
		}
	}
	
	/**
	 * Show the notification for a failed submission of a status.
	 * 
	 * @see #showNotification(int, int)
	 * */
	public void statusUploadFailed()
	{
		showNotification(R.string.notifStatusUploadFailed, STATUS_ERROR_NOTIFICATION);
	}
	
	/**
	 * Cancel any notification that is being shown when the submission of a status had failed.
	 * */
	public void statusUploadSuccess()
	{
		getNotificationManager().cancel(STATUS_ERROR_NOTIFICATION);
	}
	
	/**
	 * Shows a notification in the notification bar with the message and ID specified by parameter.
	 * 
	 * @param messageID Is the ID of the message from the resources to display on the notification.
	 * @param notificationID Is the ID of the notification.
	 * */
	private void showNotification(int messageID, int notificationID)
	{
		Intent intent = new Intent(this, TimelineActivity.class);
		PendingIntent pIntent = PendingIntent.getActivity(this, 0, intent, 0);
		Notification noti = new NotificationCompat.Builder(this)
				.setSmallIcon(R.drawable.ic_launcher_cool)
		        .setContentTitle(NOTIFICATION_TITLE)
		        .setContentText(getString(messageID))
		        .setContentIntent(pIntent).build();
		noti.flags |= Notification.FLAG_AUTO_CANCEL;
		getNotificationManager().notify(notificationID, noti);
	}
	
	
	/**
	 * Fetch the notification manager from the Android system.</br>
	 * 
	 * If the variable {@code notificationManager} is null (not initialized yet) it is initialized and returned.
	 * Otherwise, is just returned the variable because it was previously initialized.
	 * 
	 * @return The notification manager from the Android system.
	 * */
	private NotificationManager getNotificationManager()
	{
		if(notificationManager == null)
		{
			notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
		}
		return notificationManager;
	}
	
//Application methods
	
	/**
	 * Initialize a reference to the shared preferences of this application and some variables.
	 * */
	@Override
	public void onCreate()
	{
		prefs = PreferenceManager.getDefaultSharedPreferences(this);
		timelineActivity = new AtomicReference<TimelineActivity>(null);
	}

}
